package com.sprite.utils.crypto;

import com.sprite.utils.UtilString;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.Key;

/**
 * <p>对称加密，分组加密, 以128位为分组。128位明文输入，128位密文输出。</p>
 * <p>高级加密标准（英语：Advanced Encryption Standard，缩写：AES），在密码学中又称Rijndael加密法，
 * 是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES，已经被多方分析且广为全世界所使用。
 * 经过五年的甄选流程，高级加密标准由美国国家标准与技术研究院（NIST）于2001年11月26日发布于FIPS PUB 197，并
 * 在2002年5月26日成为有效的标准。2006年，高级加密标准已然成为对称密钥加密中最流行的算法之一。</p>
 *
 * <p>AES为分组密码，分组密码也就是把明文分成一组一组的，每组长度相等，每次加密一组数据，直到加密完整个明文。
 * 在AES标准规范中，分组长度只能是128位,如果最后一段不够16个字节了，就需要用Padding来把这段数据填满16个字节。
 * 密钥的长度可以使用128位、192位或256位。密钥的长度不同，推荐加密轮数也不同，如下表所示：</p>
 * <p>
 * AES	密钥长度（Byte)	分组长度(Byte)	加密轮数
 * AES-128	16	16	10
 * AES-192	24	16	12
 * AES-256	32	16	14
 *
 * <h3>Padding 说明</h3>
 * <p>Padding: PKCS5、PKCS7、 NOPADDING、ISO10126Padding </p>
 * <p>PKCS5: 分组数据缺少几个字节，就在数据的末尾填充几个字节的几，比如缺少5个字节，就在末尾填充5个字节的5;</p>
 * <p>PKCS7: 分组数据缺少几个字节，就在数据的末尾填充几个字节的0，比如缺少7个字节，就在末尾填充7个字节的0;</p>
 * <p>NOPADDING: 不需要填充，也就是说数据的发送方肯定会保证最后一段数据也正好是16个字节</p>
 * <p>ISO10126Padding: 填充字符串由一个字节序列组成，此字节序列的最后一个字节填充字节序列的长度，其余字节填充随机数据。 </p>
 * <h3>初始向量IV 说明</h3>
 * <p>初始向量IV的作用是使加密更加安全可靠，我们使用AES加密时需要主动提供初始向量，
 * 而且只需要提供一个初始向量就够了，后面每段数据的加密向量都是前面一段的密文。初始向量IV的长度规定为128位16个字节</p>
 * <h3>加密模式 说明</h3>
 * <p>ECB（Elecyronic Code Book,电子密码本模式）、CBC（Cipher Block Chaining,密码分组链接模式）、
 * CFB (Cipher FeedBack Mode加密反馈)、OFB(Output FeedBack,输出反馈)，CTR(Counter ,计算器模式),我们一般使用的是CBC模式</p>
 * <p>ECB: ECB模式是最基本的加密模式，即仅仅使用明文和密钥来加密数据，相同的明文块会被加密成相同的密文块，
 * 这样明文和密文的结构将是完全一样的，就会更容易被破解，相对来说不是那么安全，因此很少使用。</p>
 * <p>CBC: 而CBC模式则比ECB模式多了一个初始向量IV，加密的时候，第一个明文块会首先和初始向量IV做异或操作，
 * 然后再经过密钥加密，然后第一个密文块又会作为第二个明文块的加密向量来异或，依次类推下去，
 * 这样相同的明文块加密出的密文块就是不同的，明文的结构和密文的结构也将是不同的，因此更加安全，我们常用的就是CBC加密模式。</p>
 * <p>CFB: CFB的加密工作分为两部分：1、将一前段加密得到的密文再加密；2、将第1步加密得到的数据与当前段的明文异或。</p>
 * <p>OFB: OFB是先用块加密器生成密钥流（Keystream），然后再将密钥流与明文流异或得到密文流，解密是先用块加密器生成密钥流，
 * 再将密钥流与密文流异或得到明文，由于异或操作的对称性所以加密和解密的流程是完全一样的。</p>
 *
 * @author Jack
 */
public final class UtilAES {

    private static String DEFAULT_MODEL = "CBC";
    private static String DEFAULT_PADDING = "PKCS7Padding";

    /**
     * @param content
     *         内容
     * @param keyByte
     *         秘钥
     * @param ivByte
     *         偏移量
     * @param model
     *         模式
     * @param padding
     *         填充模式
     * @throws Exception
     * @return 解密结果
     */
    public static byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte, String model, String padding) throws Exception {
        Cipher cipher = Cipher.getInstance(UtilString.place("AES/{}/{}", model, padding));
        Key sKeySpec = new SecretKeySpec(keyByte, "AES");
        if ("ECB".equalsIgnoreCase(model) || ivByte == null) {
            cipher.init(Cipher.DECRYPT_MODE, sKeySpec);
        } else {
            cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));
        }

        byte[] result = cipher.doFinal(content);
        return result;
    }

    /**
     * @param content
     *         内容
     * @param keyByte
     *         秘钥
     * @param ivByte
     *         偏移量
     * @param model
     *         模式
     * @param padding
     *         填充模式
     * @throws Exception
     * @return 加密结果
     */
    public static byte[] encrypt(byte[] content, byte[] keyByte, byte[] ivByte, String model, String padding) throws Exception {
        Cipher cipher = Cipher.getInstance(UtilString.place("AES/{}/{}", model, padding));
        Key sKeySpec = new SecretKeySpec(keyByte, "AES");
        if ("ECB".equalsIgnoreCase(model) || ivByte == null) {
            cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
        } else {
            cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, generateIV(ivByte));
        }
        byte[] result = cipher.doFinal(content);
        return result;
    }


    /**
     * @param content
     *         待加密信息
     * @param keyByte
     *         秘钥
     * @param model
     *         AES加密模型
     * @param padding
     *         AES填充方式
     * @throws Exception
     * @return 加密结果
     */
    public static byte[] encrypt(byte[] content, byte[] keyByte, String model, String padding) throws Exception {
        return encrypt(content, keyByte, new byte[16], model, padding);
    }

    /**
     * @param content
     *         待加密信息
     * @param keyByte
     *         秘钥
     * @param model
     *         AES加密模型
     * @throws Exception
     * @return 加密结果
     */
    public static byte[] encrypt(byte[] content, byte[] keyByte, String model) throws Exception {
        return encrypt(content, keyByte, new byte[16], model, DEFAULT_PADDING);
    }

    /**
     * @param content
     *         待加密信息
     * @param keyByte
     *         秘钥
     * @throws Exception
     * @return 加密结果
     */
    public static byte[] encrypt(byte[] content, byte[] keyByte) throws Exception {
        return encrypt(content, keyByte, new byte[16], DEFAULT_MODEL, DEFAULT_PADDING);
    }

    /**
     * @param content
     *         待解密密信息
     * @param keyByte
     *         秘钥
     * @param model
     *         AES加密模型
     * @param padding
     *         AES填充方式
     * @throws Exception
     * @return 解密结果
     */
    public static byte[] decrypt(byte[] content, byte[] keyByte, String model, String padding) throws Exception {
        return decrypt(content, keyByte, new byte[16], model, padding);
    }

    /**
     * @param content
     *         待解密密信息
     * @param keyByte
     *         秘钥
     * @param model
     *         AES加密模型
     * @throws Exception
     * @return 解密结果
     */
    public static byte[] decrypt(byte[] content, byte[] keyByte, String model) throws Exception {
        return decrypt(content, keyByte, new byte[16], model, DEFAULT_PADDING);
    }

    /**
     * @param content
     *         待解密密信息
     * @param keyByte
     *         秘钥
     * @throws Exception
     * @return 解密结果
     */
    public static byte[] decrypt(byte[] content, byte[] keyByte) throws Exception {
        return decrypt(content, keyByte, new byte[16], DEFAULT_MODEL, DEFAULT_PADDING);
    }

    //生成iv
    private static AlgorithmParameters generateIV(byte[] iv) throws Exception {
        AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
        params.init(new IvParameterSpec(iv));
        return params;
    }

}
